/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

package br.ufrj.dcc.pushpeer.dht;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeMap;

import br.ufrj.dcc.pushpeer.protocol.PushPeer;

import peersim.core.Node;

/**
 * This class must be used for any DHT related queries across the whole simulation.  
 * @author Vitor
 *
 */
public final class SimpleDHT {
	
	private static TreeMap<Long, Node> nodes; //Specific methods (higher and lower) of the TreeMap structure are needed.
	
	private static int protocol;
	
	private SimpleDHT() {
		//Private constructor to suppress instantiation.
		throw new AssertionError();
	}
	
	/**
	 * Used to set the PushPeer protocol. Must be called before using the other methods.
	 * @param pid PushPeer's protocol id.
	 */
	public static void setProtocol(int pid) {
		protocol = pid;
	}
	
	/**
	 * Inserts a node on the DHT overlay network.
	 * @param n The node to be inserted.
	 */
	public static void insertNode(Node n) {
		if(nodes == null)
			nodes = new TreeMap<Long, Node>();
		
		nodes.put(n.getID(), n);
	}
	
	/**
	 * Removes a node from the DHT overlay network.
	 * @param n The node to be removed.
	 */
	public static void removeNode(Node n) {
		if(nodes != null)
			nodes.remove(n.getID());
	}
	
	/**
	 * Checks if the Node n is still active.
	 * @param n The node to be checked.
	 * @return true if the Node n is active, false otherwise.
	 */
	public static boolean ping(Node n) {
		return n.isUp(); //Maybe change this later if necessary...
	}
	
	/**
	 * Finds a node on the DHT overlay network that have the nearest ID to the parameter.
	 * @param id An universally unique identifier to be searched.
	 * @return A Node with the nearest ID to the one supplied.
	 */
	public static Node findNodeByID(long id) {
		if(nodes.containsKey(id)) {
			return nodes.get(id);
		}
		else {
			Long lower = nodes.lowerKey(id);
			Long higher = nodes.higherKey(id);
			if(lower == null) return nodes.higherEntry(id).getValue();
			else if (higher == null) return nodes.lowerEntry(id).getValue();
			if((id - lower) > (higher - id)) {
				return nodes.higherEntry(id).getValue();
			}
			else return nodes.lowerEntry(id).getValue();
		}
	}
	
	/**
	 * Finds the k or less nodes on the DHT overlay network that have the nearest ID to the parameter.
	 * @param id An universally unique identifier to be searched.
	 * @param k The number of nodes to be returned.
	 * @return A Set of Nodes with the nearest ID to the one supplied.
	 */
	public static Set<Node> findNodesByID(Long id, short k) {
		short returnSize = k;
		Set<Node> returnSet = new HashSet<Node>();
		
		Node currentNode = nodes.get(id);
		
		if(nodes.containsKey(id)) {
			returnSet.add(currentNode);
			returnSize--;
			
		}
		
		short halfReturnSize = (short)Math.ceil(returnSize/2);
		
		
		long currentId = id;
		
		for(int i=0; i<halfReturnSize; i++) {
			if(nodes.lowerEntry(currentId) == null) break;
			currentNode = nodes.lowerEntry(currentId).getValue();
			currentId = currentNode.getID();
			returnSize--;
			returnSet.add(currentNode);			
		}
		
		currentId = id;
		for(int i=0; i<returnSize; i++) {
			if(nodes.higherEntry(currentId) == null) break;
			currentNode = nodes.higherEntry(currentId).getValue();
			currentId = currentNode.getID();
			returnSet.add(currentNode);
		}
		
		return returnSet;
	}
	
	/**
	 * Finds k or less nodes on the DHT overlay network that have the requested resource (block of a piece of a file).
	 * @param file The file.
	 * @param piece The piece of the given file.
	 * @param block The block of the given piece.
	 * @param pid The PushPeer protocol ID.
	 * @param k The number of nodes to be returned.
	 * @return A Set of Nodes which have the requested resource.
	 */
	public static Set<Node> findNodesByFilePieceBlock(int file, int piece, int block, short k) {
		Set<Node> returnSet = new HashSet<Node>();
		
		//Shuffling the keys to simulate the pseudo-random behavior of a real DHT request.
		List<Long> keys = new ArrayList<Long>(nodes.keySet());
		Collections.shuffle(keys);
		
		for(Long key : keys) {
			Node n = nodes.get(key);
			int pieceStatus = ((PushPeer)n.getProtocol(protocol)).getFilePieceBlocksStatus(file, piece);
			if(pieceStatus >= block) {
				returnSet.add(n);
				if(returnSet.size() == k) break;
			}
		}
		
		return returnSet; 
	}
}
